package com.leyou.search.service.impl;

import com.leyou.common.enums.ExceptionEnums;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.pojo.*;
import com.leyou.search.client.BrandClient;
import com.leyou.search.client.CategoryClient;
import com.leyou.search.client.GoodsClient;
import com.leyou.search.client.SpecificationClient;
import com.leyou.search.pojo.Goods;
import com.leyou.search.pojo.SearchRequest;
import com.leyou.search.pojo.SearchResult;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.SearchService;
import com.netflix.discovery.converters.Auto;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.NumberUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private CategoryClient categoryClient;
    @Autowired
    private BrandClient brandClient;

    @Autowired
    private GoodsClient goodsClient;
    @Autowired
    private SpecificationClient specificationClient;

    @Autowired
    private GoodsRepository goodsRepository;//这个不能进行聚合查询

    @Autowired
    private ElasticsearchTemplate template;


    /**
     * 根据spu构建goods。因为索引库里存着的本质上也就是goods
     *
     * @param spu
     * @return
     */
    public Goods buildGoods(Spu spu) {

        //查询分类
        List<Category> categories = categoryClient.queryCategoryByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        if (CollectionUtils.isEmpty(categories)) {
            throw new LyException(ExceptionEnums.CATEGORY_NOT_FOND);
        }
        List<String> names = categories.stream().map(category -> category.getName()).collect(Collectors.toList());

        //查询品牌
        Brand brand = brandClient.queryBrandById(spu.getBrandId());
        if (brand == null) {
            throw new LyException(ExceptionEnums.BRAND_NOT_FOUND);
        }
        //搜索字段
        String all = spu.getTitle() + StringUtils.join(names, " ") + brand.getName();

        //价格，sku属性
        List<Sku> skuList = goodsClient.querySkuBySpuId(spu.getId());
        if (skuList == null) {
            throw new LyException(ExceptionEnums.GOODS_SKU_NOT_FOND);
        }
        //Set<Long> prices = skuList.stream().map(sku -> sku.getPrice()).collect(Collectors.toSet());
        //价格再下面的循环了一起获取，避免循环俩次
        Set<Long> prices = new HashSet<>();//存储价格
        //sku不是全部数据都要存储，所以要转化，对sku处理
        List<Map<String, Object>> skus = new ArrayList<>();//存储sku需要的字段
        for (Sku sku : skuList) {
            HashMap<String, Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("title", sku.getTitle());
            map.put("price", sku.getPrice());
            map.put("images", StringUtils.substringBefore(sku.getImages(), ","));
            //sku需要的字段
            skus.add(map);
            //价格
            prices.add(sku.getPrice());
        }
        //查询可搜索的规格参数key
        List<SpecParam> specParams = specificationClient.queryParamList(null, spu.getCid3(), true);
        if (CollectionUtils.isEmpty(specParams)) {
            throw new LyException(ExceptionEnums.SPEC_GROUP_NOT_FOND);
        }
        //查询商品详情
        SpuDetail spuDetail = goodsClient.querySpuDetailById(spu.getId());
        //规格参数,在商品详情中,商品详情key保存的是参数的id。用long
        //获取通用规格参数，key：value类型的json字符串，
        // key是stringvalue也是string类型
        Map<Long, String> generic = JsonUtils.parseMap(spuDetail.getGeneric(), Long.class, String.class);
        //获取特有规格参数，key时string值是list
        Map<Long, List> special = JsonUtils.parseMap(spuDetail.getSpecial(), Long.class, List.class);
        //存储可搜索的规格参数
        HashMap<String, Object> searchSpecMap = new HashMap();
        for (SpecParam s : specParams) {

            String key = s.getName();
            Object value = "";
            if (s.getGeneric()) {
                value = generic.get(s.getId());
                //判断是否是数字类型，是的话要分段，便于分段查询
                if (s.getNumeric()) {
                    //分段
                    value = chooseSegment(value.toString(), s);
                }
            } else {
                value = special.get(s.getId());
            }
            searchSpecMap.put(key, value);
        }

        Goods goods = new Goods();
        //spu信息
        goods.setBrandId(spu.getBrandId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setCreateTime(spu.getCreateTime());
        goods.setId(spu.getId());
        goods.setSubTitle(spu.getSubTitle());
        //搜索用的信息
        ////所有需要搜索的信息，标题,分类,品牌，规格参数一部分
        goods.setAll(all);
        //所有sku的价格集合
        goods.setPrice(prices);
        //所有sku的集合json格式
        goods.setSkus(JsonUtils.serialize(skus));
        //所有可搜索的规格参数
        goods.setSpecs(searchSpecMap);


        return goods;
    }

    /**
     * 搜索商品
     *
     * @param searchRequest
     * @return
     */
    @Override
    public SearchResult search(SearchRequest searchRequest) {
        Integer page = searchRequest.getPage() - 1;//传过来的page是从1开始，而es查询分页的page从0开始，
        int size = searchRequest.getSize();
        //查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //分页·
        queryBuilder.withPageable(PageRequest.of(page, size));
        //过滤一些字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{
                "id", "subTitle", "skus"
        }, null));
        //查询条件

        //MatchQueryBuilder baseQuery = QueryBuilders.matchQuery("all", searchRequest.getKey());
        QueryBuilder baseQuery = buildBaseQuery(searchRequest);

        queryBuilder.withQuery(baseQuery);
        //聚合，查询有哪些品牌和分类
        //分类聚合
        String categoryAggName = "category_Agg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        //品牌聚合
        String brandAggName = "brand";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        //这个查询无法化的聚合结果，需要用template对象
        // Page<Goods> result = goodsRepository.search(queryBuilder.build());
        //estemplate的方法进行聚合
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);

        //解析结果
        long total = result.getTotalElements();//总条数
        int totalPages = result.getTotalPages();//总页数
        List<Goods> goodList = result.getContent();//当前页数据
        //解析结合结果
        //1.获取所有聚合
        Aggregations aggregations = result.getAggregations();
        //2.解析分类的聚合
        List<Category> categoryList = parseCategoryAgg(aggregations.get(categoryAggName));
        //3.解析品牌聚合
        List<Brand> brandndList = parseBrand(aggregations.get(brandAggName));

        //规格参数聚合，在原来结果基础上进行聚合(全部聚合，或者对分类结果聚合饿的话，可能会居合道其他商品的参数段)
        List<Map<String, Object>> specs = null;
        if (categoryList != null && categoryList.size() == 1) {
            //分类不为空并且只有一种分类时候，聚合规格参数
            specs = buildSpecifitionAgg(categoryList.get(0).getId(), baseQuery);
        }

        return new SearchResult(total, totalPages, goodList, categoryList, brandndList, specs);
    }

    //将索引库中对应地spu商品更新,当监听到mq地消息后进行处理，不处理异常，失败就会回滚
    @Override
    public void creatsOrUpdateIndex(Long spuId) {
        final Spu spu = goodsClient.querySpuById(spuId);
        final Goods goods = buildGoods(spu);
        goodsRepository.save(goods);

    }

    @Override
    public void deleteIndex(Long spuId) {

        goodsRepository.deleteById(spuId);
    }

    //构建多条件查询
    private QueryBuilder buildBaseQuery(SearchRequest searchRequest) {

        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //查询条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", searchRequest.getKey()));
        //过滤条件
        Map<String, String> filter = searchRequest.getFilter();
        for (Map.Entry<String, String> entry : filter.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            //因为cid3和brandid是long类型的，不会被分词，直接查询就可以
            //而规格参数是string类型，并且没指定是text还是keyword，所以
            // es中保存了俩分，一份text一份keyword类型，所以我们要使用
            // keyword类型的那个来匹配，text他会进行分词，匹配不到
            //specs.key.keyword

            if (!"cid3".equals(key) && !"brandId".equals(key)) {
                key="specs."+key+".keyword";
                boolQueryBuilder.filter(QueryBuilders.termQuery(key, value));
            }else {
                boolQueryBuilder.filter(QueryBuilders.termQuery(key, NumberUtils.parseNumber(value,Long.class)));
            }

        }
        return boolQueryBuilder;
    }

    private List<Map<String, Object>> buildSpecifitionAgg(Long id, QueryBuilder baseQuery) {
        List<Map<String, Object>> specs = new ArrayList<>();
        //1.查询需要聚合的规格参数
        List<SpecParam> specParams = specificationClient.queryParamList(null, id, true);
        //2.聚合
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(baseQuery);//查询条件，
        for (SpecParam param : specParams) {
            //要注意聚合字段是specs.聚合名称.keyword,keyword表示不分词
            queryBuilder.addAggregation(AggregationBuilders.terms(param.getName()).field("specs." + param.getName() + ".keyword"));

        }
        //3.获取结果
        AggregatedPage<Goods> result = template.queryForPage(queryBuilder.build(), Goods.class);
        //4.解析结果
        Aggregations aggregations = result.getAggregations();
        for (SpecParam specParam : specParams) {
            StringTerms terms = aggregations.get(specParam.getName());
            List<String> options = terms.getBuckets()
                    .stream()
                    .map(b -> b.getKeyAsString())
                    .collect(Collectors.toList());
            Map<String, Object> map = new HashMap<>();
            map.put("key", specParam.getName());
            map.put("options", options);
            specs.add(map);
        }
        return specs;
    }

    /**
     * 根据Brandid聚合结果取数据库查询brand
     *
     * @param aggregation
     * @return
     */
    private List<Brand> parseBrand(LongTerms aggregation) {
        try {
            List<Long> ids = aggregation.getBuckets().stream().map(b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());
            List<Brand> brands = brandClient.queryBrandByIds(ids);
            return brands;
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 根据categoryid聚合结果查询数据库category
     *
     * @param aggregation
     * @return
     */
    private List<Category> parseCategoryAgg(LongTerms aggregation) {
        try {
            List<Long> ids = aggregation.getBuckets().stream().map(b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());
            List<Category> categoryList = categoryClient.queryCategoryByIds(ids);
            return categoryList;
        } catch (Exception e) {
            return null;
        }
    }


    /**
     * 判断数值类型的分段，并返回
     *
     * @param value
     * @param p
     * @return
     */
    //
    public String chooseSegment(String value, SpecParam p) {
        //NumberUtils.parseNumber();
        double v = Double.parseDouble(value);
        String result = "其他";
        for (String segment : p.getSegments().split(",")) {
            //获取并解析分段范围为double类型，用于后面判断商品是否在分段内
            String[] segs = segment.split("-");

            double begin = Double.parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = Double.parseDouble(segs[1]);
            }
            //判断是否在范围内，并把结果变成对应分段返回
            if (v >= begin && v <= end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;

            }

        }

        return result;//
    }


}
